package com.hexus.eve.entities;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.beimin.eveapi.account.characters.ApiCharacter;
import com.beimin.eveapi.character.sheet.CharacterSheetParser;
import com.beimin.eveapi.character.sheet.CharacterSheetResponse;
import com.beimin.eveapi.core.ApiAuthorization;
import com.beimin.eveapi.core.ApiException;
import com.beimin.eveapi.eve.character.CharacterInfoParser;
import com.beimin.eveapi.eve.character.CharacterInfoResponse;
import com.hexus.eve.application.Constants;
import com.hexus.eve.application.Constants.Statuses;
import org.apache.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.springframework.orm.hibernate3.HibernateTemplate;

/**
 * Class describes an EVE character.
 *   
 * @author Mikhail_Berastau
 */
@Entity
@Table(name = "account_characters")
public class Character {

	@Transient
	private Logger log = Logger.getLogger(this.getClass());

	//Availability status of character (whether it is available via EVE API or not)   
	@Transient
	private Constants.Statuses availabilityStatus = Statuses.NOT_CHECKED;
	
	//Timestamp of the latest successful check (i.e. when result was NOT 'UNAVAILABLE')
	@Transient
	private Date lastAvailabilityCheckDate = new Date();
	
	//Timestamp of the latest successful synchronization
	@Transient
	private Date lastSynchronizationDate = new Date();
	
	@Id
	@GeneratedValue
	private Integer id; //internal (database) ID of character
	
	private String name;
	private Long characterID; //EVE ID of character
	private String corporationName;
	private Long corporationID;
	
	@ManyToOne
	@JoinColumn(name = "accountId", nullable = false)
	private Account account;
	
	
	//Blueprints which this character owns
	@OneToMany
	@LazyCollection(LazyCollectionOption.FALSE)
	@JoinTable(name="characterBlueprints", joinColumns={@JoinColumn(name="characterID")}, inverseJoinColumns={@JoinColumn(name="id")})
	@Cascade(CascadeType.ALL)
	private List<CharacterBlueprint> blueprints = new ArrayList<CharacterBlueprint>();	


	/**
	 * Returns new <code>Character</code> object basing on <code>ApiCharacter</code> object
	 * @param apiCharacter - fields of this object will be used to populate corresponding result object fields 
	 * @param account - account which new object will be linked to
	 * @return new <code>Character</code> object
	 */
	public static Character convertApiCharacterToCharacter(ApiCharacter apiCharacter, Account account){
		Character character = new Character();
		character.setAccount(account);
		character.setAvailabilityStatus(Statuses.NOT_CHECKED);
		character.setCharacterID(apiCharacter.getCharacterID());
		character.setCorporationID(apiCharacter.getCorporationID());
		character.setCorporationName(apiCharacter.getCorporationName());
		character.setLastCheckDate(new Date());
		character.setName(apiCharacter.getName());
		
		return character;
	}

	
	/**
	 * Returns new <code>Character</code> object basing on <code>CharacterSheetResponse</code> object
	 * @param characterSheet - fields of this object will be used to populate corresponding result object fields 
	 * @param account - account which new object will be linked to
	 * @return new <code>Character</code> object
	 */
	public static Character convertCharacterSheetToCharacter(CharacterSheetResponse characterSheet, Account account){
		Character character = new Character();
		character.setAccount(account);
		character.setAvailabilityStatus(Statuses.NOT_CHECKED);
		character.setCharacterID(characterSheet.getCharacterID());
		character.setCorporationID(characterSheet.getCorporationID());
		character.setCorporationName(characterSheet.getCorporationName());
		character.setLastCheckDate(new Date());
		character.setName(characterSheet.getName());
		
		return character;
	}

	
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	//EVE ID of character
	public Long getCharacterID() {
		return characterID;
	}
	
	public void setCharacterID(Long characterID) {
		this.characterID = characterID;
	}
	
	public String getCorporationName() {
		return corporationName;
	}
	
	public void setCorporationName(String corporationName) {
		this.corporationName = corporationName;
	}
	
	public Long getCorporationID() {
		return corporationID;
	}
	
	public void setCorporationID(Long corporationID) {
		this.corporationID = corporationID;
	}
	
	//internal ID of character (local database ID) 
	public Integer getId() {
		return id;
	}

	public Account getAccount() {
		return account;
	}

	public void setAccount(Account account) {
		this.account = account;
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result
				+ ((characterID == null) ? 0 : characterID.hashCode());
		result = prime * result
				+ ((corporationID == null) ? 0 : corporationID.hashCode());
		result = prime * result
				+ ((corporationName == null) ? 0 : corporationName.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Character other = (Character) obj;
		if (characterID == null) {
			if (other.characterID != null)
				return false;
		} else if (!characterID.equals(other.characterID))
			return false;
		if (corporationID == null) {
			if (other.corporationID != null)
				return false;
		} else if (!corporationID.equals(other.corporationID))
			return false;
		if (corporationName == null) {
			if (other.corporationName != null)
				return false;
		} else if (!corporationName.equals(other.corporationName))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	
	
	/**
	 * Update of character's availability status.<br/>
	 * Method checks if character information can be read via EVE API.<br/>
	 * Last check result can be retrieved by calling <code>getAvailabilityStatus()</code> method. 
	 * @return Result of the check (current status) 
	 */
	public Statuses updateAvailabilityStatus() {
		Statuses s = Statuses.NOT_CHECKED;
		CharacterInfoParser cip = CharacterInfoParser.getInstance();
		try {
			//ApiAuthorization auth = new ApiAuthorization(account.getUserId(), -1, account.getApiKey());
			CharacterInfoResponse cir = cip.getResponse(getCharacterID());
			if (cir != null) {
				if(cir.hasError()) {
					s = Statuses.UNAVAILABLE;
					if(log.isDebugEnabled()) {
						log.debug("Character availability status response error: " + cir.getError() + " Character ID=" + getId() + ", Name=" + getName());
					}
				}
				else {
					s = Statuses.AVAILABLE;
				}
			}
		} catch (ApiException e) {
			if(log.isDebugEnabled()) {
				log.debug(e.getMessage());
				e.printStackTrace();
			}
		}

		availabilityStatus = s;
		
		if(s != Statuses.NOT_CHECKED)
			lastAvailabilityCheckDate = new Date();
		
		return s;
	}

	
	/**
	 * Allows to check if availability check status is up to date basing on the time passed since the last check.
	 * If status is outdated, it should be updated by calling the <code>updateAvailabilityStatus()</code> method
	 *   
	 * @return <code>true</code> - if availability status is up to date
	 */
	public Boolean isAvailabilityStatusUpToDate(){
		Integer syncInterval = Integer.parseInt(Constants.getInstance().getProperty(Constants.PropertyNames.CHARACTER_AVAILABILITY_CHECK_INTERVAL));
		return (new Date()).getTime() - getLastCheckDate().getTime() > (syncInterval * 1000);
	}
	

	/**
	 * Allows to check if last synchronization is up to date basing on the time passed since then.<br/>
	 * If it's outdated, character need to be synchronized by calling the <code>synchronizeWithEve()</code> method
	 *   
	 * @return <code>true</code> - if availability status is up to date
	 */
	public Boolean isSynchronizationUpToDate(){
		Integer syncInterval = Integer.parseInt(Constants.getInstance().getProperty(Constants.PropertyNames.CHARACTER_SYNCHRONIZATION_INTERVAL));
		return (new Date()).getTime() - getLastSynchronizationDate().getTime() > (syncInterval * 1000);
	}

	
	/**
	 * Updates local character with information of the corresponding character from EVE
	 */
	public void synchronizeWithEve(SessionFactory sessionFactory) {
		if(!isAvailabilityStatusUpToDate()) {
			updateAvailabilityStatus();
		}
		
		if(getAvailabilityStatus() == Statuses.AVAILABLE) {
			CharacterSheetParser csp = CharacterSheetParser.getInstance();
			ApiAuthorization auth = new ApiAuthorization(account.getUserId(), characterID, account.getApiKey());
			try {
				CharacterSheetResponse csr = csp.getResponse(auth);
				if(csr.hasError()) {
					if(log.isDebugEnabled()) {
						log.debug("CharacterSheetResponse error: " + csr.getError() + "; Character ID=" + getId() + ", name=" + getName());
					}
				} else {
					Character eveCharacter = Character.convertCharacterSheetToCharacter(csr, account);
					if(!eveCharacter.equals(this)) { //character records differ, synchronization is required
						copyAllFields(eveCharacter);
						HibernateTemplate ht = new HibernateTemplate(sessionFactory);
						ht.update(this);						
					};
				}
			} catch (ApiException e) {
				if(log.isDebugEnabled()) {
					log.debug(e.getMessage());
					e.printStackTrace();
				}
			}
			//ApiCharacter apiCharacter = ApiCharacter
			//convertApiCharacterToCharacter(apiCharacter, account);
		}
	}

	
	/**
	 * Copies all fields of another <code>Character</code> object to this object.<br/>
	 * Used for synchronization with EVE characters
	 */
	private void copyAllFields(Character from) {
		setCharacterID(from.getCharacterID());
		setCorporationID(from.getCorporationID());
		setCorporationName(from.getCorporationName());
		setName(from.getCorporationName());
	} 
	
	
	public Constants.Statuses getAvailabilityStatus() {
		return availabilityStatus;
	}

	public void setAvailabilityStatus(Constants.Statuses availabilityStatus) {
		this.availabilityStatus = availabilityStatus;
	}

	public Date getLastCheckDate() {
		return lastAvailabilityCheckDate;
	}

	public void setLastCheckDate(Date lastCheckDate) {
		this.lastAvailabilityCheckDate = lastCheckDate;
	}

	public Date getLastSynchronizationDate() {
		return lastSynchronizationDate;
	}

	public void setLastSynchronizationDate(Date lastSynchronizationDate) {
		this.lastSynchronizationDate = lastSynchronizationDate;
	}

	public List<CharacterBlueprint> getBlueprints() {
		return blueprints;
	}

	public void setBlueprints(List<CharacterBlueprint> blueprints) {
		this.blueprints = blueprints;
	}
	
}
